/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Reflection;
using Microsoft.Scripting.Ast;

namespace Microsoft.Scripting.Actions {
    public abstract class MetaCallSiteBinder : CallSiteBinder {
        public sealed override Rule<T> Bind<T>(object[] args) {
            if (!typeof(Delegate).IsAssignableFrom(typeof(T))) {
                throw new InvalidOperationException("RuleBuilder generic argument must be a delegate");
            }

            ParameterInfo[] pis = typeof(T).GetMethod("Invoke").GetParameters();

            if (pis.Length == 0 || pis[0].ParameterType != typeof(CallSite)) {
                throw new InvalidOperationException("RuleBuilder can only be used with delegates whose first argument is CallSite");
            }

            ParameterExpression[] pes;
            Expression[] expressions = MakeParameters(pis, out pes);

            MetaObject[] mos = new MetaObject[args.Length];
            for (int i = 0; i < mos.Length; i++) {
                IDynamicObject2 ido = args[i] as IDynamicObject2;
                if (ido != null) {
                    mos[i] = ido.GetMetaObject(expressions[i]);
                } else {
                    mos[i] = new ParameterMetaObject(expressions[i], args[i]);
                }
            }

            MetaObject binding = Bind(mos);

            return new Rule<T>(
                Expression.Scope(
                    GetMetaObjectRule(binding),
                    "<rule>"
                ),
                null,
                new ReadOnlyCollection<ParameterExpression>(pes)
            );
        }

        private static Expression GetMetaObjectRule(MetaObject binding) {
            Expression body = Expression.Return(binding.Expression);
            
            if (binding.Restrictions != Restrictions.Empty) {
                // add the test only if we have one
                body = Expression.Condition(
                    binding.Restrictions.CreateTest(),
                    body,
                    Expression.Empty()
                );
            }

            return body;
        }

        public abstract MetaObject Bind(params MetaObject[] args);

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "args")]
        public MetaObject Defer(params MetaObject[] args) {
            throw new NotImplementedException();
        }

        private Expression[] MakeParameters(ParameterInfo[] pis, out ParameterExpression[] parameters) {
            // First argument is the dynamic site
            const int FirstParameterIndex = 1;

            Expression[] all = new Expression[pis.Length - FirstParameterIndex];
            ParameterExpression[] vars = new ParameterExpression[pis.Length - FirstParameterIndex];

            for (int i = FirstParameterIndex; i < pis.Length; i++) {
                int index = i - FirstParameterIndex;
                all[index] = vars[index] = Expression.Parameter(pis[i].ParameterType, "$arg" + index);
            }

            parameters = vars;
            return all;
        }

        internal static MetaObject ExtractCC(ref MetaObject[] mos) {
            if (mos.Length > 0) {
                MetaObject mo = mos[0];
                if (mo.RuntimeType == typeof(Microsoft.Scripting.Runtime.CodeContext)) {
                    mos = Microsoft.Scripting.Utils.ArrayUtils.RemoveFirst(mos);
                    return mo;
                }
            }

            // Return something marginally useful???
            return null;
        }

    }
}
